#include "burner.h"
#include <tlhelp32.h>
#include <setupapi.h>
#include <psapi.h>

#ifdef _MSC_VER
#pragma comment(lib, "setupapi")
#endif

// these are not defined in the Cygwin/MinGW winapi package
#ifndef DISPLAY_DEVICE_ACTIVE
 #define DISPLAY_DEVICE_ACTIVE              0x00000001
#endif
#ifndef DISPLAY_DEVICE_ATTACHED
 #define DISPLAY_DEVICE_ATTACHED            0x00000002
#endif

static _EXCEPTION_POINTERS* pExceptionPointers;

static wchar_t* pszTextBuffer = NULL;
static int nTextBufferSize = 0;

static int nRecursion = 0;

static HFONT hLogFont, hCodeFont;
static HBRUSH hCodeBGBrush;

static const bool bPrintDriverInfo = false;

static int AddLine(wchar_t* pszFormat, ...)
{
	wchar_t szString[128];

	va_list vaFormat;
	va_start(vaFormat, pszFormat);

	int nLen = _vsnwprintf(szString, 70, pszFormat, vaFormat);
	nLen = (nLen >= 0 && nLen < 70) ? nLen : 70;
	nLen += swprintf(szString + nLen, L"\r\n");
	wchar_t* pszNewBuffer = (wchar_t*)realloc(pszTextBuffer, (nLen + nTextBufferSize + 1) * sizeof(wchar_t));
	if (pszNewBuffer) {
		pszTextBuffer = pszNewBuffer;
		wcsncpy(pszTextBuffer + nTextBufferSize, szString, nLen);
		nTextBufferSize += nLen;
		pszTextBuffer[nTextBufferSize] = 0;
	}

	va_end(vaFormat);

	return 0;
}

static int AddText(wchar_t* pszFormat, ...)
{
	wchar_t szString[128] = L"";

	va_list vaFormat;
	va_start(vaFormat, pszFormat);

	int nLen = _vsnwprintf(szString, 70, pszFormat, vaFormat);
	nLen = (nLen >= 0 && nLen < 70) ? nLen : 70;
	wchar_t* pszNewBuffer = (wchar_t*)realloc(pszTextBuffer, (nLen + nTextBufferSize + 1) * sizeof(wchar_t));
	if (pszNewBuffer) {
		pszTextBuffer = pszNewBuffer;
		wcsncpy(pszTextBuffer + nTextBufferSize, szString, nLen);
		nTextBufferSize += nLen;
		pszTextBuffer[nTextBufferSize] = 0;
	}

	va_end(vaFormat);

	return 0;
}

static int PrintInterfaceInfo(InterfaceInfo* pInfo)
{
	if (pInfo == NULL) {
		return 1;
	}

	if (pInfo->pszModuleName) {
		AddLine(L"    Selected module:    %s", pInfo->pszModuleName);
	}
	for (int i = 0; pInfo->ppszInterfaceSettings[i]; i++) {
		AddLine(L"    %s%s", (i == 0) ? L"Interface settings: " : L"                    ", pInfo->ppszInterfaceSettings[i]);
	}
	for (int i = 0; pInfo->ppszModuleSettings[i]; i++) {
		AddLine(L"    %s%s", (i == 0) ? L"Module settings:    " : L"                    ", pInfo->ppszModuleSettings[i]);
	}

	return 0;
}

// Print information about the exception
int PrintExceptionInfo()
{
	static const struct { DWORD ExceptionCode; const wchar_t* szString; } ExceptionString[] = {

#define EXCEPTION_LIST_ENTRY(exception) { exception, L#exception }

		EXCEPTION_LIST_ENTRY(EXCEPTION_ACCESS_VIOLATION),
		EXCEPTION_LIST_ENTRY(EXCEPTION_ARRAY_BOUNDS_EXCEEDED),
		EXCEPTION_LIST_ENTRY(EXCEPTION_BREAKPOINT),
		EXCEPTION_LIST_ENTRY(EXCEPTION_DATATYPE_MISALIGNMENT),
		EXCEPTION_LIST_ENTRY(EXCEPTION_FLT_DENORMAL_OPERAND),
		EXCEPTION_LIST_ENTRY(EXCEPTION_FLT_DIVIDE_BY_ZERO),
		EXCEPTION_LIST_ENTRY(EXCEPTION_FLT_INEXACT_RESULT),
		EXCEPTION_LIST_ENTRY(EXCEPTION_FLT_INVALID_OPERATION),
		EXCEPTION_LIST_ENTRY(EXCEPTION_FLT_OVERFLOW),
		EXCEPTION_LIST_ENTRY(EXCEPTION_FLT_STACK_CHECK),
		EXCEPTION_LIST_ENTRY(EXCEPTION_FLT_UNDERFLOW),
		EXCEPTION_LIST_ENTRY(EXCEPTION_ILLEGAL_INSTRUCTION),
		EXCEPTION_LIST_ENTRY(EXCEPTION_IN_PAGE_ERROR),
		EXCEPTION_LIST_ENTRY(EXCEPTION_INVALID_DISPOSITION),
		EXCEPTION_LIST_ENTRY(EXCEPTION_INT_DIVIDE_BY_ZERO),
		EXCEPTION_LIST_ENTRY(EXCEPTION_INT_OVERFLOW),
		EXCEPTION_LIST_ENTRY(EXCEPTION_INVALID_HANDLE),
		EXCEPTION_LIST_ENTRY(EXCEPTION_GUARD_PAGE),
		EXCEPTION_LIST_ENTRY(EXCEPTION_NONCONTINUABLE_EXCEPTION),
		EXCEPTION_LIST_ENTRY(EXCEPTION_PRIV_INSTRUCTION),
		EXCEPTION_LIST_ENTRY(EXCEPTION_SINGLE_STEP),
		EXCEPTION_LIST_ENTRY(EXCEPTION_STACK_OVERFLOW),
		{ 0, L"unspecified exception" }

#undef EXCEPTION_LIST_ENTRY

	};

	int i;

	for (i = 0; ExceptionString[i].ExceptionCode; i++) {
		if (ExceptionString[i].ExceptionCode == pExceptionPointers->ExceptionRecord->ExceptionCode) {
			break;
		}
	}

	AddLine(L"Exception 0x%08X (%s) thrown.\r\nEIP: 0x%p", pExceptionPointers->ExceptionRecord->ExceptionCode, ExceptionString[i].szString, pExceptionPointers->ExceptionRecord->ExceptionAddress);
	if (pExceptionPointers->ExceptionRecord->ExceptionCode ==  EXCEPTION_ACCESS_VIOLATION) {
		AddLine(L" (attempting to %s address 0x%p)", pExceptionPointers->ExceptionRecord->ExceptionInformation[0] ? L"write" : L"read", pExceptionPointers->ExceptionRecord->ExceptionInformation[1]);
	}
	AddLine(L"");

	return 0;
}

// Print OS information
int PrintOSInfo()
{
	OSVERSIONINFOEX osvi;
	memset(&osvi, 0, sizeof(OSVERSIONINFOEX));

	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
	if (!GetVersionEx((OSVERSIONINFO*)&osvi)) {
		osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
		if (!GetVersionEx((OSVERSIONINFO*)&osvi))
			return 1;
	}

	SYSTEM_INFO si;
	memset(&si, 0, sizeof(SYSTEM_INFO));
	GetSystemInfo(&si);

	AddText(L"OS:  ");
	{
		if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) {
			if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0) {
				AddText(L"Microsoft Windows 2000 ");
			}
			else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1) {
				AddText(L"Microsoft Windows XP ");
			}
			else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2) {
				if (osvi.wProductType == VER_NT_WORKSTATION
					&& si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) {
					AddText(L"Microsoft Windows XP Professional x64 Edition ");
				}
//				else if (GetSystemMetrics(SM_SERVERR2)) {
//					AddText(_T("Microsoft Windows Server 2003 \"R2\" "));
//				}
				else
					AddText(L"Microsoft Windows Server 2003 ");
			}
			else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0) {
				 if (osvi.wProductType == VER_NT_WORKSTATION )
					AddText(L"Windows Vista ");
				 else
				 	AddText(L"Windows Server \"Longhorn\" ");
			}
			else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1) {
				if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) {
					AddText(L"Windows 7 x64 ");
				} else {
					AddText(L"Windows 7 ");
				}
			}
			else if (osvi.dwMajorVersion != 5 || osvi.dwMinorVersion > 2) {
				AddText(L"Microsoft Windows NT %d.%d ", osvi.dwMajorVersion, osvi.dwMinorVersion);
			}

			if (osvi.wProductType == VER_NT_WORKSTATION) {
				if (osvi.wSuiteMask & VER_SUITE_PERSONAL) {
					AddText(L"Personal ");
				} else {
					AddText(L"Professional ");
				}
			}
			if (osvi.wProductType == VER_NT_SERVER) {
				if (osvi.wSuiteMask & VER_SUITE_DATACENTER) {
					AddText(L"DataCenter Server ");
				} else {
					if (osvi.wSuiteMask & VER_SUITE_DATACENTER) {
						AddText(L"Advanced Server ");
					} else {
						AddText(L"Server ");
					}
				}
			}
			AddText(L"%s (build %i)\r\n", osvi.szCSDVersion, osvi.dwBuildNumber & 0xFFFF);
		}
	}

	return 0;
}

int PrintCPUInfo()
{
	// Determine the processor type using the CPUID instruction
	DWORD nSpeed = 0;
	HKEY hKey;

	// Get the OS hardware info
	SYSTEM_INFO si;
	GetSystemInfo(&si);

	// Determine speed (read from the registry as there's no reliable way to determine it on modern systems)
	if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_EXECUTE, &hKey) == ERROR_SUCCESS) {
		DWORD nType = REG_DWORD;
		DWORD size = sizeof(nSpeed);

		RegQueryValueEx(hKey, L"~MHz", NULL, &nType, (BYTE*)&nSpeed, &size);
		RegCloseKey(hKey);
	}

	// Determine extensions supported (MMX, etc.)
	bool bMMX = false, bXMMI = false, bXMMI64 = false;
	{
		BOOL (WINAPI* pIsProcessorFeaturePresent)(DWORD) = NULL;
		HMODULE hKernel32DLL = LoadLibrary(L"kernel32.dll");

		if (hKernel32DLL) {
			pIsProcessorFeaturePresent = (BOOL (WINAPI*)(DWORD))GetProcAddress(hKernel32DLL, "IsProcessorFeaturePresent");
			if (pIsProcessorFeaturePresent) {
				if (pIsProcessorFeaturePresent(PF_MMX_INSTRUCTIONS_AVAILABLE)) {
					bMMX = true;
				}
				if (pIsProcessorFeaturePresent(PF_XMMI_INSTRUCTIONS_AVAILABLE)) {
					bXMMI = true;
				}
				if (pIsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE)) {
					bXMMI64 = true;
				}
			}
			FreeLibrary(hKernel32DLL);
		}
	}

	AddText(L"CPU: ");

	if (nSpeed) {
		AddText(L"     %i MHz", nSpeed);
	} else {
		AddText(L"     unknown speed");
	}
	if (bMMX || bXMMI || bXMMI64) {
		AddText(L"%hs%hs%hs", bMMX ? ", MMX" : "", bXMMI ? ", SSE" : "", bXMMI64 ? ", SSE2" : "");
	}
	AddText(L" (%i system processor%s)\r\n)", si.dwNumberOfProcessors, si.dwNumberOfProcessors > 1 ? "s" : "");

	return 0;
}

// Print global memory information
int PrintGlobalMemoryInfo()
{
	MEMORYSTATUS stat;
	GlobalMemoryStatus(&stat);

	AddLine(L"Physical RAM: %7i KB (%4i MB) total, %7i KB (%4i MB) avail", stat.dwTotalPhys / 1024, stat.dwTotalPhys / (1024 * 1024), stat.dwAvailPhys / 1024, stat.dwAvailPhys / (1024 * 1024));
	AddLine(L"Total RAM:    %7i KB (%4i MB) total, %7i KB (%4i MB) avail", stat.dwTotalPageFile / 1024, stat.dwTotalPageFile / (1024 * 1024), stat.dwAvailPageFile / 1024, stat.dwAvailPageFile / (1024 * 1024));

	// Information on FB Alpha memory usage
	BOOL (WINAPI* pGetProcessMemoryInfo)(HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD) = NULL;
	HMODULE hPsapiDLL = LoadLibrary(L"psapi.dll");
	if (hPsapiDLL) {
		pGetProcessMemoryInfo = (BOOL (WINAPI*)(HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD))GetProcAddress(hPsapiDLL, "GetProcessMemoryInfo");
		if (pGetProcessMemoryInfo) {
			PROCESS_MEMORY_COUNTERS pmc;

			if (pGetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
				wchar_t strLine[1024] = L"";
				size_t length = wcslen(APP_TITLE);
				if (length > 12) {
					length = 12;
				}
				_snwprintf(strLine, 12, APP_TITLE);
				_snwprintf(strLine + length, 14 - length, L":                 ");
				AddLine(L"%s%7i KB in use (%i KB peak, %i KB virtual)", strLine, pmc.WorkingSetSize / 1024, pmc.PeakWorkingSetSize / 1024, pmc.PagefileUsage / 1024);
			}
		}
		FreeLibrary(hPsapiDLL);
	}

	return 0;
}

// Find the registry key for this display in the hardware area of the registry
// szHardwareID is the PNP ID
// szDriver     is the GUID/instance of the driver
HKEY FindMonitor(wchar_t* szHardwareID, wchar_t* szDriver)
{
	wchar_t szName[1024] = L"";  DWORD nNameSize = sizearray(szName);
	wchar_t szClass[1024] = L""; DWORD nClassSize = sizearray(szClass);
	FILETIME ftLastWriteTime;
	HKEY hKey = NULL;
	HKEY hMonitorKey = NULL;

	bool bFound = false;

	// We need to enumerate all displays and all instances to check the values inside them
	RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Enum\\DISPLAY\\", 0, KEY_READ, &hKey);
	for (int i = 0; !bFound && RegEnumKeyEx(hKey, i, szName, &nNameSize, NULL, szClass, &nClassSize, &ftLastWriteTime) != ERROR_NO_MORE_ITEMS; i++) {
		wchar_t szSubName[1024] = L"";  DWORD nSubNameSize = sizearray(szSubName);
		wchar_t szSubClass[1024] = L""; DWORD nSubClassSize = sizearray(szSubClass);
		FILETIME ftSubLastWriteTime;
		HKEY hSubKey = NULL;

		nNameSize = sizearray(szName);
		nClassSize = sizearray(szClass);

		RegOpenKeyEx(hKey, szName, 0, KEY_READ, &hSubKey);
		for (int j = 0; !bFound && RegEnumKeyEx(hSubKey, j, szSubName, &nSubNameSize, NULL, szSubClass, &nSubClassSize, &ftSubLastWriteTime) != ERROR_NO_MORE_ITEMS; j++) {
			HKEY hMonitorInfoKey = NULL;
			wchar_t szKeyValue[1024]; DWORD size = sizearray(szKeyValue);
			DWORD nType;

			nSubNameSize = sizearray(szSubName);
			nSubClassSize = sizearray(szSubClass);

			RegOpenKeyEx(hSubKey, szSubName, 0, KEY_READ, &hMonitorKey);

			// Check if this instance is the one we're looking for
			nType = REG_SZ; size = sizearray(szKeyValue);
			RegQueryValueEx(hMonitorKey, L"HardwareID", NULL, &nType, (BYTE*)szKeyValue, &size);
			if (_wcsicmp(szKeyValue, szHardwareID)) {
				continue;
			}
			nType = REG_SZ; size = sizearray(szKeyValue);
			RegQueryValueEx(hMonitorKey, L"Driver", NULL, &nType, (BYTE*)szKeyValue, &size);
			if (_wcsicmp(szKeyValue, szDriver)) {
				continue;
			}

			// Make sure the "Device Parameters" key which contains any EDID data is present
			RegOpenKeyEx(hMonitorKey, L"Device Parameters", 0, KEY_READ, &hMonitorInfoKey);
			if (!hMonitorInfoKey) {
				continue;
			}

			// We've found the display we're looking for
			bFound = true;

			RegCloseKey(hMonitorInfoKey);
		}
		RegCloseKey(hSubKey);
	}
	RegCloseKey(hKey);

	if (!bFound) {
		return NULL;
	}

	return hMonitorKey;
}

int ProcessEDID(HKEY hMonitorKey)
{
	HKEY hMonitorInfoKey = NULL;
	BYTE EDIDData[1024]; DWORD nEDIDSize = sizearray(EDIDData);
	DWORD nType;

	if (hMonitorKey == NULL) {
		return 1;
	}

	RegOpenKeyEx(hMonitorKey, L"Device Parameters", 0, KEY_READ, &hMonitorInfoKey);
	if (hMonitorInfoKey == NULL) {
		return 1;
	}

	// When Windows can't get valid EDID data from a Display, it creates a BAD_EDID value instead of EDID
	// Thus we can forego ensuring the validity of the EDID data ourselves
	nType = REG_BINARY; nEDIDSize = sizearray(EDIDData);
	if (RegQueryValueEx(hMonitorInfoKey, L"BAD_EDID", NULL, &nType, EDIDData, &nEDIDSize) == 0) {
		AddLine(L"        No EDID data present for this device");
	}
	nType = REG_BINARY; nEDIDSize = sizearray(EDIDData);
	if (RegQueryValueEx(hMonitorInfoKey, L"EDID", NULL, &nType, EDIDData, &nEDIDSize) == 0) {

		// Print some basic information about this display
		AddLine(L"        Display size ~%dx%dcm, Gamma %1.2lf", EDIDData[0x15], EDIDData[0x16], ((double)EDIDData[0x17] + 100.0) / 100.0);

		// Print the preferred mode for this display
		if (EDIDData[0x18] & 2) {
			int nPixelClock = ((EDIDData[0x36 + 0x01] << 8) | EDIDData[0x36 + 0x00]) * 10000;

			// Size of the display image in pixels (including blanking and sync)
			int nActiveH = ((EDIDData[0x36 + 0x04] & 0xF0) << 4) | EDIDData[0x36 + 0x02];
			int nBlankH  = ((EDIDData[0x36 + 0x04] & 0x0F) << 8) | EDIDData[0x36 + 0x03];
			int nActiveV = ((EDIDData[0x36 + 0x07] & 0xF0) << 4) | EDIDData[0x36 + 0x05];
			int nBlankV  = ((EDIDData[0x36 + 0x07] & 0x0F) << 8) | EDIDData[0x36 + 0x06];

			// Size of the display image in mm
			int nSizeH = ((EDIDData[0x36 + 0x0E] & 0xF0) << 4) | EDIDData[0x36 + 0x0C];
			int nSizeV = ((EDIDData[0x36 + 0x0E] & 0x0F) << 8) | EDIDData[0x36 + 0x0D];

			// We need to calculate the refresh rate ourselves based on the other numbers
			double dRefresh = 1.0 / ((double)(nActiveH + nBlankH) * (nActiveV + nBlankV) / nPixelClock);

			AddLine(L"        Preferred mode %dx%d, %1.3lf Hz (%dx%dmm, %1.3lf MHz)", nActiveH, nActiveV, dRefresh, nSizeH, nSizeV, nPixelClock / 1000000.0);
		}

		{
			// Print the signal limits for this display

			int nLimitsOffset = 0;

			// Find the data block containing the limits
			for (int i = 0; i < 4; i++) {
				if (EDIDData[0x36 + i * 0x12 + 0] == 0x00 && EDIDData[0x36 + i * 0x12 + 1] == 0x00 && EDIDData[0x36 + i * 0x12 + 2] == 0x00 && EDIDData[0x36 + i * 0x12 + 3] == 0xFD) {
					nLimitsOffset = 0x36 + i * 0x12;
					break;
				}
			}

			if (nLimitsOffset) {
				AddLine(L"        Max. bandwidth %d MHz, H sync %d-%d KHz, V sync %d-%d Hz", EDIDData[nLimitsOffset + 0x09] * 10, EDIDData[nLimitsOffset + 0x07], EDIDData[nLimitsOffset + 0x08], EDIDData[nLimitsOffset + 0x05], EDIDData[nLimitsOffset + 0x06]);
			}
		}
	}

	RegCloseKey(hMonitorInfoKey);

	return 0;
}

// Print info about displays and display adapters
int PrintDisplayInfo()
{
	// The EnumDisplayDevices() function is only available on NT based OSes
	BOOL (WINAPI* pEnumDisplayDevices)(LPCTSTR, DWORD, PDISPLAY_DEVICE, DWORD) = NULL;
	HMODULE hUser32DLL = LoadLibrary(L"user32.dll");
	if (hUser32DLL) {
		pEnumDisplayDevices = (BOOL (WINAPI*)(LPCTSTR, DWORD, PDISPLAY_DEVICE, DWORD))GetProcAddress(hUser32DLL, "EnumDisplayDevicesW");
		if (pEnumDisplayDevices) {
			DISPLAY_DEVICE ddAdapter;
			DISPLAY_DEVICE ddDisplay;

			ddAdapter.cb = sizeof(DISPLAY_DEVICE);
			ddDisplay.cb = sizeof(DISPLAY_DEVICE);

			// Now that we've ensured we can use the EnumDisplayDevices() function, use it to enumerate the connected displays
			AddLine(L"Installed displays and display adapters:");

			for (int i = 0; pEnumDisplayDevices(NULL, i, &ddAdapter, 0); i++) {

				// We're only interested in real display adapters
				if (!(ddAdapter.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)) {
					for (int j = 0; pEnumDisplayDevices(ddAdapter.DeviceName, j, &ddDisplay, 0); j++) {

						HKEY hKey = NULL;
						HKEY hMonitorKey = NULL;

						wchar_t szMfg[1024] = L"";		  DWORD nMfgSize = sizearray(szMfg);
						wchar_t szDeviceDesc[1024] = L""; DWORD nDeviceDescSize = sizearray(szDeviceDesc);

						// If the display is active, get the data about it
						if (ddDisplay.StateFlags & DISPLAY_DEVICE_ACTIVE) {
							if (!_wcsnicmp(ddDisplay.DeviceKey, L"\\Registry\\Machine\\", 18)) {
								wchar_t szDriver[1024]; DWORD size = sizearray(szDriver);
								DWORD nType = REG_SZ;
								RegOpenKeyEx(HKEY_LOCAL_MACHINE, ddDisplay.DeviceKey + 18, 0, KEY_READ, &hKey);

								// Find the registry key for this display in the hardware area of the registry
								RegQueryValueEx(hKey, L"MatchingDeviceId", NULL, &nType, (BYTE*)szDriver, &size);
								hMonitorKey = FindMonitor(szDriver, ddDisplay.DeviceKey + 57);

								RegQueryValueEx(hMonitorKey, L"DeviceDesc", NULL, &nType, (BYTE*)szDeviceDesc, &nDeviceDescSize);
								RegQueryValueEx(hMonitorKey, L"Mfg", NULL, &nType, (BYTE*)szMfg, &nMfgSize);
							}

							// Print the information we've got so far
							wchar_t* pszStatus = L"";
							if (ddAdapter.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {
								pszStatus = L" (primary)";
							}
							if (!(ddAdapter.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)) {
								pszStatus = L" (disabled)";
							}
							if (szMfg[0] && szDeviceDesc[0]) {
								AddLine(L"    %s %s on %s%s", szMfg, szDeviceDesc, ddAdapter.DeviceString, pszStatus);
							} else {
								AddLine(L"    %s on %s%s", ddDisplay.DeviceString, ddAdapter.DeviceString, pszStatus);
							}

							ProcessEDID(hMonitorKey);

							if (hMonitorKey) {
								RegCloseKey(hMonitorKey);
							}
							if (hKey) {
								RegCloseKey(hKey);
							}
						}
					}
				}
			}
		}
		FreeLibrary(hUser32DLL);
	}

	return 0;
}

// Print FB Alpha settings
int PrintFBAInfo()
{
	InterfaceInfo* pInfo;

	AddLine(APP_TITLE L" information:");
	AddLine(L"");

	AddLine(L"    Using Unicode for all text.");
#if defined (FBA_DEBUG)
	AddLine(L"    Debug functionality present.");
#else
	AddLine(L"    Debug functionality absent.");
#endif
	AddLine(L"");

	AddLine(L"Musashi emulation core enabled for MC680x0 family emulation.");
	AddLine(L"MAME Z80 emulation core enabled for Z80 emulation.");
	AddLine(L"");

	if (driverOkay) {
		wchar_t szName[1024];

		int n = swprintf(szName, L"Emulating %hs (%hs)", BurnDrvGetTextA(DRV_NAME), BurnDrvGetTextA(DRV_FULLNAME));
		if (n >= 70) {
			wcscpy(szName + 66, L"...)");
		}
		AddLine(L"%s", szName);
		AddLine(L"    Vertical refresh is %.2lf Hz.", (double)BurnGetRefreshRate() / 100);
		AddLine(L"    CPU running at %i%% of normal frequency.", nBurnCPUSpeedAdjust * 100 / 256);
		AddLine(L"");
	} else {
		AddLine(L"Not emulating any game.");
		AddLine(L"");
	}

	if ((pInfo = videoInterface.get()) != NULL) {
		AddLine(L"Video settings:");
		PrintInterfaceInfo(pInfo);
	}
	AddLine(L"");

	if ((pInfo = audioInterface.get()) != NULL) {
		AddLine(L"Audio settings:");
		PrintInterfaceInfo(pInfo);
	}
	AddLine(L"");

	if ((pInfo = inputInterface.get()) != NULL) {
		AddLine(L"Input settings:");
		PrintInterfaceInfo(pInfo);
	}

	return 0;
}

// Print process information
int PrintProcessInfo()
{
#if 1
	HANDLE hModuleSnap;
	MODULEENTRY32 me32;

	hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
	if (hModuleSnap == INVALID_HANDLE_VALUE) {
		AddLine(L"Unable to retrieve detailed process information.");
	} else {
		me32.dwSize = sizeof(MODULEENTRY32);
		if(!Module32First(hModuleSnap, &me32)) {
			AddLine(L"Unable to retrieve detailed process information.");
		} else {
			AddLine(L"Detailed process information about %s:", me32.szModule);
			AddLine(L"");
			AddLine(L"%s (base address 0x%p, size %i KB)", me32.szModule, me32.modBaseAddr, me32.modBaseSize / 1024);
			AddLine(L"");

			if (pExceptionPointers) {
				bool bFound = false;
				do {
					if (me32.modBaseAddr <= pExceptionPointers->ExceptionRecord->ExceptionAddress && (me32.modBaseAddr + me32.modBaseSize) > pExceptionPointers->ExceptionRecord->ExceptionAddress) {
						bFound = true;
						break;
					}
				} while (Module32Next(hModuleSnap, &me32));

				if (bFound) {
					AddLine(L"Exception occurred in module %s:", me32.szModule);
					AddLine(L"    %20s (base address 0x%p, size %6i KB)", me32.szModule, me32.modBaseAddr, me32.modBaseSize / 1024);
					AddLine(L"");
				} else {
					AddLine(L"Unable to locate module in which exception occurred");
					AddLine(L"");
				}
			}

			Module32First(hModuleSnap, &me32);
			AddLine(L"Modules loaded by %s:", me32.szModule);
			while (Module32Next(hModuleSnap, &me32)){
				AddLine(L"    %20s (base address 0x%p, size %6i KB)", me32.szModule, me32.modBaseAddr, me32.modBaseSize / 1024);
			}
		}

		CloseHandle(hModuleSnap);
	}
#endif

	return 0;
}

// Print information about installed devices
int PrintDeviceInfo()
{
	// Get a list of all devices that are present and enabled
	HDEVINFO hDevInfo = SetupDiGetClassDevs(NULL, NULL, NULL, DIGCF_ALLCLASSES | DIGCF_PRESENT | DIGCF_PROFILE);
	if (hDevInfo != INVALID_HANDLE_VALUE) {
		SC_HANDLE (WINAPI* pOpenSCManager)(LPCTSTR, LPCTSTR, DWORD) = NULL;
		SC_HANDLE (WINAPI* pOpenService)(SC_HANDLE, LPCTSTR, DWORD) = NULL;
		BOOL (WINAPI* pQueryServiceConfig)(SC_HANDLE, LPQUERY_SERVICE_CONFIG, DWORD, LPDWORD) = NULL;
		BOOL (WINAPI* pCloseServiceHandle)(SC_HANDLE) = NULL;

		HMODULE hAdvapi32DLL = LoadLibrary(L"advapi32.dll");
		if (hAdvapi32DLL) {
			pOpenSCManager = (SC_HANDLE (WINAPI*)(LPCTSTR, LPCTSTR, DWORD))GetProcAddress(hAdvapi32DLL, "OpenSCManagerW");
			pOpenService = (SC_HANDLE (WINAPI*)(SC_HANDLE, LPCTSTR, DWORD))GetProcAddress(hAdvapi32DLL, "OpenServiceW");
			pQueryServiceConfig = (BOOL (WINAPI*)(SC_HANDLE, LPQUERY_SERVICE_CONFIG, DWORD, LPDWORD))GetProcAddress(hAdvapi32DLL, "QueryServiceConfigW");
			pCloseServiceHandle = (BOOL (WINAPI*)(SC_HANDLE))GetProcAddress(hAdvapi32DLL, "CloseServiceHandle");
		}

		AddLine(L"Installed devices (partial list):");
		AddLine(L"");

		// Enumerate all devices in the list
		SP_DEVINFO_DATA did;
		did.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
		for (int i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &did); i++) {
			wchar_t* list[] = { L"display", L"media", L"hid", L"hidclass", L"mouse", L"system", NULL };
			wchar_t szClass[1024] = L"";

			// Get the installer class of a device
			SetupDiGetDeviceRegistryProperty(hDevInfo, &did, SPDRP_CLASS, NULL, (BYTE*)szClass, 1024, NULL);

			// determine if the device is of a class we're interested in
			for (int j = 0; list[j]; j++) {
				if (!_wcsicmp(list[j], szClass)) {
					wchar_t szName[1024] = L"";
					wchar_t szService[1024] = L"";
					wchar_t szDriverDate[1024] = L"";
					wchar_t szDriverVersion[1024] = L"";
					wchar_t szImagePath[1024] = L"";
					HKEY hKey;

					// Get the device name
					SetupDiGetDeviceRegistryProperty(hDevInfo, &did, SPDRP_DEVICEDESC, NULL, (BYTE*)szName, 1024, NULL);

					// Check if there are any lower filter drivers
					SetupDiGetDeviceRegistryProperty(hDevInfo, &did, SPDRP_LOWERFILTERS, NULL, (BYTE*)szService, 1024, NULL);
					if (szService[0] == '\0') {
						// If we haven't got any lower filter drivers, just use the service key
						SetupDiGetDeviceRegistryProperty(hDevInfo, &did, SPDRP_SERVICE, NULL, (BYTE*)szService, 1024, NULL);
					}

					// Get driver info
					hKey = SetupDiOpenDevRegKey(hDevInfo, &did, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_READ);
					if (hKey != INVALID_HANDLE_VALUE) {
						DWORD nType = REG_SZ;
						DWORD size = 1024;

						RegQueryValueEx(hKey, L"DriverVersion", NULL, &nType, (BYTE*)szDriverVersion, &size);
						RegQueryValueEx(hKey, L"DriverDate", NULL, &nType, (BYTE*)szDriverDate, &size);
						RegCloseKey(hKey);
					}

					// If we have a driver, get the filename
					if (szService[0] && pOpenSCManager && pOpenService && pQueryServiceConfig && pCloseServiceHandle) {
						SC_HANDLE hSCManager, hService;
						QUERY_SERVICE_CONFIG* pSC = NULL;
						DWORD size;

						hSCManager = OpenSCManager(NULL, NULL, GENERIC_READ);
						hService = OpenService(hSCManager, szService, GENERIC_READ);

						QueryServiceConfig(hService, NULL, 0, &size);
						pSC = new QUERY_SERVICE_CONFIG[size];

						if (QueryServiceConfig(hService, pSC, size, &size)) {
							wcscpy(szImagePath, pSC->lpBinaryPathName);
						}

						delete [] pSC;

						CloseServiceHandle(hService);
						CloseServiceHandle(hSCManager);
					}

					// Print the information
					if (j < 2 || szImagePath[0]) {
						AddLine(L"    %s", szName);
						AddLine(L"        %s", szImagePath[0] ? szImagePath : L"no driver needed");

						if (szDriverVersion[0]) {
							AddLine(L"        version %s (%s)", szDriverVersion, szDriverDate[0] ? szDriverDate : L"no date");
						}
					}

					break;
				}
			}
		}

		FreeLibrary(hAdvapi32DLL);

		SetupDiDestroyDeviceInfoList(hDevInfo);
	}

	return 0;
}

static INT_PTR CALLBACK SysInfoProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	static int returnCode = 0;

	switch (msg) {
		case WM_INITDIALOG: {
		    time_t nTime;
			tm* tmTime;

			returnCode = 0;

			pszTextBuffer = NULL;
			nTextBufferSize = 0;

			time(&nTime);
			tmTime = localtime(&nTime);

			hLogFont = CreateFont(14, 0, 0, 0, FW_NORMAL, 0, 0, 0, 0, 0, 0, DEFAULT_QUALITY, FF_MODERN, L"");

			hCodeBGBrush = NULL;
			hCodeFont = NULL;

			if (pExceptionPointers) {
				wchar_t szText[1024];
				hCodeBGBrush = CreateSolidBrush(RGB(0,0,0));
				hCodeFont = CreateFont(22, 0, 0, 0, FW_BOLD, 0, 0, 0, 0, 0, 0, ANTIALIASED_QUALITY, FF_MODERN, L"Lucida Console");
				SendDlgItemMessage(hDlg, IDC_SYSINFO_CODE, WM_SETFONT, (WPARAM)hCodeFont, 0);

				swprintf(szText, L"Fatal exception #%08X.%08X!", pExceptionPointers->ExceptionRecord->ExceptionCode, (unsigned int)pExceptionPointers->ExceptionRecord->ExceptionAddress);
				SendDlgItemMessage(hDlg, IDC_SYSINFO_CODE, WM_SETTEXT, 0, (LPARAM)szText);

				AddLine(APP_TITLE L" v%.20s fatal exception report (%s).", appBurnVer, _wasctime(tmTime));

				AddLine(L"");
				AddLine(L"--------------------------------------------------------------------");

				// Exception
				PrintExceptionInfo();
			} else {
				AddLine(APP_TITLE L" v%.20s system information (%s).", appBurnVer, _wasctime(tmTime));
			}

			AddLine(L"");
			AddLine(L"--------------------------------------------------------------------");

			AddLine(L"System information:");
			AddLine(L"");

			// OS information
			PrintOSInfo();

			// CPU information
			PrintCPUInfo();

			AddLine(L"");

			// Global memory information
			PrintGlobalMemoryInfo();

			AddLine(L"");

			// Displays and display adapters
			PrintDisplayInfo();

			AddLine(L"");
			AddLine(L"--------------------------------------------------------------------");

			PrintFBAInfo();

			AddLine(L"");
			AddLine(L"--------------------------------------------------------------------");

			// Process information
			PrintProcessInfo();

			AddLine(L"");
			AddLine(L"--------------------------------------------------------------------");

			if (bPrintDriverInfo) {
				// Device information
				PrintDeviceInfo();

				AddLine(L"");
				AddLine(L"--------------------------------------------------------------------");
			}

			SendDlgItemMessage(hDlg, IDC_SYSINFO_EDIT, WM_SETFONT, (WPARAM)hLogFont, 0);
			SendDlgItemMessage(hDlg, IDC_SYSINFO_EDIT, EM_SETMARGINS, (WPARAM)EC_LEFTMARGIN, (LPARAM)3);
			SendDlgItemMessage(hDlg, IDC_SYSINFO_EDIT, WM_SETTEXT, 0, (LPARAM)pszTextBuffer);

			wndInMid(hDlg, hScrnWnd);

			return TRUE;
		}

		case WM_CLOSE: {
			free(pszTextBuffer);
			pszTextBuffer = NULL;
			nTextBufferSize = 0;

			DeleteObject(hLogFont);
			if (hCodeFont) {
				DeleteObject(hCodeFont);
			}
			if (hCodeBGBrush) {
				DeleteObject(hCodeBGBrush);
			}
			EndDialog(hDlg, returnCode);
			break;
		}

		case WM_CTLCOLORSTATIC: {
			if ((HWND)lParam == GetDlgItem(hDlg, IDC_SYSINFO_EDIT)) {
				return (BOOL)GetSysColorBrush(15);
			}
			if ((HWND)lParam == GetDlgItem(hDlg, IDC_SYSINFO_CODE)) {
				SetTextColor((HDC)wParam, RGB(255, 0, 0));
				SetBkMode((HDC)wParam, TRANSPARENT);
				return (BOOL)hCodeBGBrush;
			}
			break;
		}

		case WM_COMMAND: {
			if (HIWORD(wParam) == BN_CLICKED) {
				if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
					SendMessage(hDlg, WM_CLOSE, 0, 0);
				}
				if (LOWORD(wParam) == IDC_SYSINFO_DEBUG) {
					returnCode = 1;
					SendMessage(hDlg, WM_CLOSE, 0, 0);
				}
				if (LOWORD(wParam) == IDC_SYSINFO_SHOW && pExceptionPointers) {
					RECT rect = { 0, 0, 0, 125 };

					SendMessage(hDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hDlg, IDOK), TRUE);
					EnableWindow(GetDlgItem(hDlg, IDC_SYSINFO_SHOW), FALSE);

					MapDialogRect(hDlg, &rect);
					int size = rect.bottom;
					GetWindowRect(hDlg, &rect);
					MoveWindow(hDlg, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top + size, TRUE);

					wndInMid(hDlg, hScrnWnd);
				}
				if (LOWORD(wParam) == IDC_SYSINFO_LOG) {
					FILE* fp = NULL;

					SendMessage(hDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hDlg, IDOK), TRUE);

					if (pExceptionPointers) {
						wchar_t szLogName[MAX_PATH];
						swprintf(szLogName, L"config\\%s.error.log", appExeName);
						fp = _wfopen(szLogName, L"ab");

						EnableWindow(GetDlgItem(hDlg, IDC_SYSINFO_LOG), FALSE);
					} else {
						wchar_t szFilter[1024];
						swprintf(szFilter, localiseStringEx(IDS_DISK_TEXT));
						memcpy(szFilter + wcslen(szFilter), L" (*.txt)\0*.txt\0\0", 16 * sizeof(wchar_t));

						swprintf(commonChoice, L"%s system information.txt", appExeName);
						if (!commonFileDialog(hDlg, true, localiseStringEx(IDS_DISK_SAVEREPORT), szFilter, 0, L"txt")) {
							fp = _wfopen(commonChoice, L"wb");
						}
					}

					if (fp) {
						int size = SendDlgItemMessage(hDlg, IDC_SYSINFO_EDIT, WM_GETTEXTLENGTH, 0, 0);
						wchar_t* szText = new wchar_t[size + 1];

						// write a Unicode Byte Order Mark if needed
						if (ftell(fp) == 0) {
							WRITE_UNICODE_BOM(fp);
						}

						if (size && szText) {
							SendDlgItemMessage(hDlg, IDC_SYSINFO_EDIT, WM_GETTEXT, (WPARAM)(size + 1) * sizeof(wchar_t), (LPARAM)szText);
							fwrite(szText, sizeof(wchar_t), size, fp);
							fwprintf(fp, L"");
						}
						delete [] szText;
						fclose(fp);
					}
				}
			}
		}
	}

	return 0;
}

LONG CALLBACK ExceptionFilter(_EXCEPTION_POINTERS* pExceptionInfo)
{
	// If we're getting recursive calls to this function, bail out
	if (nRecursion++) {
		if (nRecursion <= 2) {
			MessageBox(hScrnWnd, APP_TITLE L" will now be terminated.", APP_TITLE L" Fatal exception", MB_OK | MB_SETFOREGROUND);
			appCleanup();
		}
#ifdef _DEBUG
		return EXCEPTION_CONTINUE_SEARCH;
#else
		return EXCEPTION_EXECUTE_HANDLER;
#endif
	}

	splashDestroy(1);
	audioInterface.stop();

	pExceptionPointers = pExceptionInfo;

	int ret = localiseDialogBox(IDD_EXCEPTION, hScrnWnd, (DLGPROC)SysInfoProc);

	switch (ret) {
		case 1:
			return EXCEPTION_CONTINUE_SEARCH;
		default:
			appCleanup();
			return EXCEPTION_EXECUTE_HANDLER;
	}
}

int systemInfoCreate(HWND parent)
{
	pExceptionPointers = NULL;
	localiseDialogBox(IDD_SYSINFO, parent, (DLGPROC)SysInfoProc);
	return 0;
}
